package com.navinfo.parkingnavi.widget;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.Scroller;

import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import com.navinfo.parkingnavi.R;
import com.navinfo.parkingnavi.util.SystemUtil;
import com.navinfo.parkingnavi.widget.content.ContentScrollView;

/*
 *com.navinfo.parkingnavi.widget
 *zhjch
 *2020/5/18
 *11:08
 *说明（）
 */
public class ScrollLayout extends FrameLayout {

    private static final int MAX_SCROLL_DURATION = 400;

    private static final int MIN_SCROLL_DURATION = 100;

    private static final int FLING_VELOCITY_SLOP = 80;

    private static final float DRAG_SPEED_MULTIPLIER = 1.2f;

    private static final int DRAG_SPEED_SLOP = 30;

    private static final int MOTION_DISTANCE_SLOP = 10;

    private static final float SCROLL_TO_CLOSE_OFFSET_FACTOR = 0.5f;

    private static final float SCROLL_TO_EXIT_OFFSET_FACTOR = 0.8f;

    private final GestureDetector.OnGestureListener gestureListener =

            new GestureDetector.SimpleOnGestureListener() {

                @Override

                public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

                    if (velocityY > FLING_VELOCITY_SLOP) {

                        if (lastFlingStatus.equals(Status.OPENED) && -getScrollY() > maxOffset) {

                            lastFlingStatus = Status.EXIT;

                            scrollToExit();

                        } else {

                            scrollToOpen();

                            lastFlingStatus = Status.OPENED;

                        }

                        return true;

                    } else if (velocityY < FLING_VELOCITY_SLOP && getScrollY() <= -maxOffset) {

                        scrollToOpen();

                        lastFlingStatus = Status.OPENED;

                        return true;

                    } else if (velocityY < FLING_VELOCITY_SLOP && getScrollY() > -maxOffset) {

                        scrollToClose();

                        lastFlingStatus = Status.CLOSED;

                        return true;

                    }

                    return false;

                }

            };


    private final AbsListView.OnScrollListener associatedListViewListener =

            new AbsListView.OnScrollListener() {

                @Override

                public void onScrollStateChanged(AbsListView view, int scrollState) {

                    updateListViewScrollState(view);

                }


                @Override

                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,

                                     int totalItemCount) {

                    updateListViewScrollState(view);

                }

            };

    private final RecyclerView.OnScrollListener associatedRecyclerViewListener =

            new RecyclerView.OnScrollListener() {

                @Override

                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

                    super.onScrollStateChanged(recyclerView, newState);

                    updateRecyclerViewScrollState(recyclerView);

                }


                @Override

                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

                    super.onScrolled(recyclerView, dx, dy);

                    updateRecyclerViewScrollState(recyclerView);

                }

            };


    private float lastX;

    private float lastY;

    private float lastDownX;

    private float lastDownY;

    private Status lastFlingStatus = Status.CLOSED;

    private Scroller scroller;

    private GestureDetector gestureDetector;

    private boolean isEnable = true;

    private boolean isSupportExit = false;

    private boolean isAllowHorizontalScroll = true;

    private boolean isDraggable = true;

    private boolean isAllowPointerIntercepted = true;

    private boolean isCurrentPointerIntercepted = false;

    private InnerStatus currentInnerStatus = InnerStatus.OPENED;

    private int maxOffset = 0;

    public int minOffset = 0;

    private int exitOffset = 0;

    private OnScrollChangedListener onScrollChangedListener;

    private ContentScrollView mScrollView;

    private int screenH;

    public ScrollLayout(Context context) {
        super(context);

    }


    public ScrollLayout(Context context, AttributeSet attrs) {

        super(context, attrs);

        initFromAttributes(context, attrs);

    }


    public ScrollLayout(Context context, AttributeSet attrs, int defStyle) {

        super(context, attrs, defStyle);

        initFromAttributes(context, attrs);

    }


    {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

            scroller = new Scroller(getContext(), null, true);

        } else {

            scroller = new Scroller(getContext());

        }

        gestureDetector = new GestureDetector(getContext(), gestureListener);

    }


    private void initFromAttributes(Context context, AttributeSet attrs) {

        screenH = SystemUtil.getScreenHeight((Activity) context);
        TypedArray a = context.obtainStyledAttributes(attrs,

                R.styleable.ScrollLayout);

        if (a.hasValue(R.styleable.ScrollLayout_maxOffset)) {

            int maxset = a.getDimensionPixelOffset(R.styleable.ScrollLayout_maxOffset, maxOffset);

            if (maxset != screenH) {

                maxOffset = screenH - maxset;

            }

        }

        if (a.hasValue(R.styleable.ScrollLayout_minOffset))

            minOffset = a.getDimensionPixelOffset(R.styleable.ScrollLayout_minOffset, minOffset);

        if (a.hasValue(R.styleable.ScrollLayout_exitOffset)) {

            int exitset = a.getDimensionPixelOffset(R.styleable.ScrollLayout_exitOffset, screenH);

            if (exitset != screenH) {

                exitOffset = screenH - exitset;

            }

        }

        if (a.hasValue(R.styleable.ScrollLayout_allowHorizontalScroll))

            isAllowHorizontalScroll = a.getBoolean(R.styleable.ScrollLayout_allowHorizontalScroll, true);

        if (a.hasValue(R.styleable.ScrollLayout_isSupportExit))

            isSupportExit = a.getBoolean(R.styleable.ScrollLayout_isSupportExit, true);

        if (a.hasValue(R.styleable.ScrollLayout_mode)) {

            int mode = a.getInteger(R.styleable.ScrollLayout_mode, 0);

            switch (mode) {

                case 0x0:

                    setToOpen();

                    break;

                case 0x1:

                    setToClosed();

                    break;

                case 0x2:

                    setToExit();

                    break;

                default:

                    setToClosed();

                    break;

            }

        }

        a.recycle();

    }


    private ContentScrollView.OnScrollChangedListener mOnScrollChangedListener = new ContentScrollView.OnScrollChangedListener() {

        @Override

        public void onScrollChanged(int l, int t, int oldL, int oldT) {

            if (null == mScrollView) return;

            if (null != onScrollChangedListener)

                onScrollChangedListener.onChildScroll(oldT);

            if (mScrollView.getScrollY() == 0) {

                setDraggable(true);

            } else {

                setDraggable(false);

            }

        }

    };


    /**
     * Set the scrolled position of your view. This will cause a call to
     * <p>
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * <p>
     * invalidated.
     *
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */

    @Override

    public void scrollTo(int x, int y) {

        super.scrollTo(x, y);

        if (maxOffset == minOffset) {

            return;

        }

        //only from min to max or from max to min,send progress out. not exit

        if (-y <= maxOffset) {

            float progress = (float) (-y - minOffset) / (maxOffset - minOffset);

            onScrollProgressChanged(progress);

        } else {

            float progress = (float) (-y - maxOffset) / (maxOffset - exitOffset);

            onScrollProgressChanged(progress);

        }

        if (y == -minOffset) {

            // closed

            if (currentInnerStatus != InnerStatus.CLOSED) {

                currentInnerStatus = InnerStatus.CLOSED;

                onScrollFinished(Status.CLOSED);

            }

        } else if (y == -maxOffset) {

            // opened

            if (currentInnerStatus != InnerStatus.OPENED) {

                currentInnerStatus = InnerStatus.OPENED;

                onScrollFinished(Status.OPENED);

            }

        } else if (isSupportExit && y == -exitOffset) {

            // exited

            if (currentInnerStatus != InnerStatus.EXIT) {

                currentInnerStatus = InnerStatus.EXIT;

                onScrollFinished(Status.EXIT);

            }

        }

    }


    private void onScrollFinished(Status status) {

        if (onScrollChangedListener != null) {

            onScrollChangedListener.onScrollFinished(status);

        }

    }


    private void onScrollProgressChanged(float progress) {

        if (onScrollChangedListener != null) {

            onScrollChangedListener.onScrollProgressChanged(progress);

        }

    }


    @Override

    public void computeScroll() {

        if (!scroller.isFinished() && scroller.computeScrollOffset()) {

            int currY = scroller.getCurrY();

            scrollTo(0, currY);

            if (currY == -minOffset || currY == -maxOffset || (isSupportExit && currY == -exitOffset)) {

                scroller.abortAnimation();

            } else {

                invalidate();

            }

        }

    }


    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (!isEnable) {

            return false;

        }

        if (!isDraggable && currentInnerStatus == InnerStatus.CLOSED) {

            return false;

        }

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:

                lastX = ev.getX();

                lastY = ev.getY();

                lastDownX = lastX;

                lastDownY = lastY;

                isAllowPointerIntercepted = true;

                isCurrentPointerIntercepted = false;

                if (!scroller.isFinished()) {

                    scroller.forceFinished(true);

                    currentInnerStatus = InnerStatus.MOVING;

                    isCurrentPointerIntercepted = true;

                    return true;

                }

                break;

            case MotionEvent.ACTION_UP:

            case MotionEvent.ACTION_CANCEL:

                isAllowPointerIntercepted = true;

                isCurrentPointerIntercepted = false;

                if (currentInnerStatus == InnerStatus.MOVING) {

                    return true;

                }

                break;

            case MotionEvent.ACTION_MOVE:

                if (!isAllowPointerIntercepted) {

                    return false;

                }

                if (isCurrentPointerIntercepted) {

                    return true;

                }

                int deltaY = (int) (ev.getY() - lastDownY);

                int deltaX = (int) (ev.getX() - lastDownX);

                if (Math.abs(deltaY) < MOTION_DISTANCE_SLOP) {

                    return false;

                }

                if (Math.abs(deltaY) < Math.abs(deltaX)) {

                    // horizontal event

                    if (isAllowHorizontalScroll) {

                        isAllowPointerIntercepted = false;

                        isCurrentPointerIntercepted = false;

                        return false;

                    }

                }

                if (currentInnerStatus == InnerStatus.CLOSED) {

                    // when closed, only handle downwards motion event

                    if (deltaY < 0) {

                        // upwards

                        return false;

                    }

                } else if (currentInnerStatus == InnerStatus.OPENED && !isSupportExit) {

                    // when opened, only handle upwards motion event

                    if (deltaY > 0) {

                        // downwards

                        return false;

                    }

                }

                isCurrentPointerIntercepted = true;

                return true;

            default:

                return false;

        }

        return false;

    }


    @Override

    public boolean onTouchEvent(MotionEvent event) {

        if (!isCurrentPointerIntercepted) {

            return false;

        }

        gestureDetector.onTouchEvent(event);

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:

                lastY = event.getY();

                return true;

            case MotionEvent.ACTION_MOVE:

                int deltaY = (int) ((event.getY() - lastY) * DRAG_SPEED_MULTIPLIER);
                deltaY = (int) (Math.signum(deltaY)) * Math.min(Math.abs(deltaY), DRAG_SPEED_SLOP);
                if (disposeEdgeValue(deltaY)) {
                    return true;

                }

                currentInnerStatus = InnerStatus.MOVING;

                int toScrollY = getScrollY() - deltaY;

                if (toScrollY >= -minOffset) {

                    scrollTo(0, -minOffset);

                } else if (toScrollY <= -maxOffset && !isSupportExit) {

                    scrollTo(0, -maxOffset);

                } else {

                    scrollTo(0, toScrollY);

                }

                lastY = event.getY();

                return true;

            case MotionEvent.ACTION_UP:

            case MotionEvent.ACTION_CANCEL:

                if (currentInnerStatus == InnerStatus.MOVING) {

                    completeMove();

                    return true;

                }

                break;

            default:

                return false;

        }

        return false;

    }


    private boolean disposeEdgeValue(int deltaY) {

        if (isSupportExit) {

            if (deltaY <= 0 && getScrollY() >= -minOffset) {

                return true;

            } else if (deltaY >= 0 && getScrollY() <= -exitOffset) {

                return true;

            }

        } else {

            if (deltaY <= 0 && getScrollY() >= -minOffset) {

                return true;

            } else if (deltaY >= 0 && getScrollY() <= -maxOffset) {

                return true;

            }

        }

        return false;

    }


    private void completeMove() {

        float closeValue = -((maxOffset - minOffset) * SCROLL_TO_CLOSE_OFFSET_FACTOR);

        if (getScrollY() > closeValue) {

            scrollToClose();

        } else {

            if (isSupportExit) {

                float exitValue = -((exitOffset - maxOffset) * SCROLL_TO_EXIT_OFFSET_FACTOR + maxOffset);

                if (getScrollY() <= closeValue && getScrollY() > exitValue) {

                    scrollToOpen();

                } else {

                    scrollToExit();

                }

            } else {

                scrollToOpen();

            }

        }

    }


    /**
     * 滚动布局打开关闭,关闭否则滚动.
     */

    public void showOrHide() {

        if (currentInnerStatus == InnerStatus.OPENED) {

            scrollToClose();

        } else if (currentInnerStatus == InnerStatus.CLOSED) {

            scrollToOpen();

        }

    }


    /**
     * 滚动布局开放,maxOffset之后向下滚动.
     */

    public void scrollToOpen() {

        if (currentInnerStatus == InnerStatus.OPENED) {

            return;

        }

        if (maxOffset == minOffset) {

            return;

        }

        int dy = -getScrollY() - maxOffset;

        if (dy == 0) {

            return;

        }

        currentInnerStatus = InnerStatus.SCROLLING;

        int duration = MIN_SCROLL_DURATION

                + Math.abs((MAX_SCROLL_DURATION - MIN_SCROLL_DURATION) * dy / (maxOffset - minOffset));

        scroller.startScroll(0, getScrollY(), 0, dy, duration);

        invalidate();

    }


    /**
     * 滚动的布局来关闭,滚动到minOffset.
     */

    public void scrollToClose() {

        if (currentInnerStatus == InnerStatus.CLOSED) {

            return;

        }

        if (maxOffset == minOffset) {

            return;

        }

        int dy = -getScrollY() - minOffset;

        if (dy == 0) {

            return;

        }

        currentInnerStatus = InnerStatus.SCROLLING;

        int duration = MIN_SCROLL_DURATION

                + Math.abs((MAX_SCROLL_DURATION - MIN_SCROLL_DURATION) * dy / (maxOffset - minOffset));

        scroller.startScroll(0, getScrollY(), 0, dy, duration);

        invalidate();

    }


    /**
     * 滚动布局退出
     */

    public void scrollToExit() {

        if (!isSupportExit) return;

        if (currentInnerStatus == InnerStatus.EXIT) {

            return;

        }

        if (exitOffset == maxOffset) {

            return;

        }

        int dy = -getScrollY() - exitOffset;

        if (dy == 0) {

            return;

        }

        currentInnerStatus = InnerStatus.SCROLLING;

        int duration = MIN_SCROLL_DURATION

                + Math.abs((MAX_SCROLL_DURATION - MIN_SCROLL_DURATION) * dy / (exitOffset - maxOffset));

        scroller.startScroll(0, getScrollY(), 0, dy, duration);

        invalidate();

    }


    /**
     * 初始化布局开放,没有动画。
     */

    public void setToOpen() {

        scrollTo(0, -maxOffset);

        currentInnerStatus = InnerStatus.OPENED;

        lastFlingStatus = Status.OPENED;

    }


    /**
     * 初始化布局关闭,没有动画。
     */

    public void setToClosed() {

        scrollTo(0, -minOffset);

        currentInnerStatus = InnerStatus.CLOSED;

        lastFlingStatus = Status.CLOSED;

    }


    /**
     * 初始化布局,退出,没有动画。
     */

    public void setToExit() {

        if (!isSupportExit) return;

        scrollTo(0, -exitOffset);

        currentInnerStatus = InnerStatus.EXIT;

    }


    public void setMinOffset(int minOffset) {

        this.minOffset = minOffset;

    }


    public void setMaxOffset(int maxOffset) {

        this.maxOffset = screenH - maxOffset;

    }


    public void setExitOffset(int exitOffset) {

        this.exitOffset = screenH - exitOffset;

    }


    public void setEnable(boolean enable) {

        this.isEnable = enable;

    }


    public void setIsSupportExit(boolean isSupportExit) {

        this.isSupportExit = isSupportExit;

    }


    public boolean isSupportExit() {

        return isSupportExit;

    }


    public boolean isAllowHorizontalScroll() {

        return isAllowHorizontalScroll;

    }


    public void setAllowHorizontalScroll(boolean isAllowed) {

        isAllowHorizontalScroll = isAllowed;

    }


    public boolean isDraggable() {

        return isDraggable;

    }


    public void setDraggable(boolean draggable) {

        this.isDraggable = draggable;

    }


    public void setOnScrollChangedListener(OnScrollChangedListener listener) {

        this.onScrollChangedListener = listener;

    }


    public Status getCurrentStatus() {

        switch (currentInnerStatus) {

            case CLOSED:

                return Status.CLOSED;

            case OPENED:

                return Status.OPENED;

            case EXIT:

                return Status.EXIT;

            default:

                return Status.OPENED;

        }

    }


    /**
     * Set associated list view, then this layout will only be able to drag down when the list
     * <p>
     * view is scrolled to top.
     *
     * @param listView
     */

    public void setAssociatedListView(AbsListView listView) {

        listView.setOnScrollListener(associatedListViewListener);

        updateListViewScrollState(listView);

    }


    /**
     * Set associated list view, then this layout will only be able to drag down when the list
     * <p>
     * view is scrolled to top.
     *
     * @param recyclerView
     */

    public void setAssociatedRecyclerView(RecyclerView recyclerView) {

        recyclerView.addOnScrollListener(associatedRecyclerViewListener);

        updateRecyclerViewScrollState(recyclerView);

    }


    private void updateListViewScrollState(AbsListView listView) {

        if (listView.getChildCount() == 0) {

            setDraggable(true);

        } else {

            if (listView.getFirstVisiblePosition() == 0) {

                View firstChild = listView.getChildAt(0);

                if (firstChild.getTop() == listView.getPaddingTop()) {

                    setDraggable(true);

                    return;

                }

            }

            setDraggable(false);

        }

    }


    private void updateRecyclerViewScrollState(RecyclerView recyclerView) {

        if (recyclerView.getChildCount() == 0) {

            setDraggable(true);

        } else {

            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

            int[] i = new int[1];

            if (layoutManager instanceof LinearLayoutManager || layoutManager instanceof GridLayoutManager) {

                i[0] = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();

            } else if (layoutManager instanceof StaggeredGridLayoutManager) {

                i = null;

                i = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(i);

            }

            if (i[0] == 0) {

                View firstChild = recyclerView.getChildAt(0);

                if (firstChild.getTop() == recyclerView.getPaddingTop()) {

                    setDraggable(true);

                    return;

                }

            }

            setDraggable(false);

        }

    }


    public void setAssociatedScrollView(ContentScrollView scrollView) {

        this.mScrollView = scrollView;

        this.mScrollView.setScrollbarFadingEnabled(false);

        this.mScrollView.setOnScrollChangeListener(mOnScrollChangedListener);

    }


    private enum InnerStatus {

        EXIT, OPENED, CLOSED, MOVING, SCROLLING

    }





    /**
     * 表明Scrolllayout的状态,只可以打开或关闭。
     */

    public enum Status {

        EXIT, OPENED, CLOSED

    }


    /**
     * 注册这个Scrolllayout可以监控其滚动
     */

    public interface OnScrollChangedListener {

        /**
         * 每次滚动改变值
         *
         * @param currentProgress 0 to 1, 1 to -1, 0 means close, 1 means open, -1 means exit.
         */

        void onScrollProgressChanged(float currentProgress);


        /**
         * 滚动状态改变时调用的方法
         *
         * @param currentStatus the current status after change
         */

        void onScrollFinished(Status currentStatus);


        /***
         * 滚动子视图
         *
         * @param top the child view scroll data

         */

        void onChildScroll(int top);

    }


}